/*
 * CodeInterpreter.java
 * Copyright (C) 12/29/2005, 18:08 Francisco J. Hernández
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 */

package Sindarin.CodeInterpreter;

import NativeRobot.NativeRobot;
import Sindarin.Common.*;
import Sindarin.ScriptParser.ScriptParser;
import Sindarin.ScriptParser.ScriptParserConstants;
import java.awt.Toolkit;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.Calendar;
import java.util.Stack;
import java.util.Vector;

/**
 *
 * @author Francisco J. Hernández
 */
public class CodeInterpreter implements Runnable {
    SimbolTable table;
    Vector inst = new Vector();
    Stack stack = new Stack();
    NativeRobot robot;
    
    private Location locTmp;
    private MacroKey keyTmp;
    //private Integer numTmp;
    private String strTmp;
    private Simbol simbTmp;
    private int []teclasTmp;
    private int i;
    
    private int pc;
    private boolean paused = false;
    private boolean stopped = false;
    
    private Thread thread = null;
    
    private static Calendar calendar = Calendar.getInstance();
    private static Toolkit tool = java.awt.Toolkit.getDefaultToolkit();
    
    /** Creates a new instance of CodeInterpreter */
    public CodeInterpreter(Vector nInst, SimbolTable table, NativeRobot robot) {
        this.inst = nInst;
        this.table = table;
        thread = new Thread (this , "Ejecucion de codigo en hilo");
        //thread.setPriority(Thread.MAX_PRIORITY);
        thread.setPriority(Thread.NORM_PRIORITY);
        this.robot = robot;
        robot.setAutoDelay(4);
    }
    
    public Vector getInst() {
        return inst;
    }
    
    public void start() {
        thread.start();
    }

    public void run() {
        pc = 0;
        if (!inst.isEmpty())
            while ((pc < inst.size()) && (pc >= 0) && (!stopped)) {
                try {
                    while (paused)
                        //wait(5);
                        thread.sleep(5);
                } catch (InterruptedException ex) {
                    System.err.println(java.util.ResourceBundle.getBundle("Sindarin/config").getString("ERROR_PAUSE_THREAD") + ex);
                }
                execActInst();
            }
        stopped = false;
        paused = false;
    }
    
    public synchronized void execActInst() {
        Inst instAct = ((Inst)inst.elementAt(pc));
        switch (instAct.opcode) {
            case Inst.LOADC:
                if (instAct.operador instanceof String) {
                    strTmp = (String)instAct.operador;
                    strTmp = strTmp.substring(1, strTmp.length()-1);
                    stack.push(strTmp);
                }
                else
                    stack.push(instAct.operador);
                break;
            case Inst.LOAD:
                simbTmp = table.find((String)instAct.operador);  
                stack.push(simbTmp.value);
                break;      
            case Inst.STORE:
                simbTmp = table.find((String)instAct.operador);
                simbTmp.value = stack.pop();
                break;
            case Inst.INC:
                stack.push(new Integer(((Integer)stack.pop()).intValue()+1));
                break;                
            case Inst.DEC:
                stack.push(new Integer(((Integer)stack.pop()).intValue()-1));
                break;
            case Inst.MULC:
                Integer res = new Integer( ((Integer)instAct.operador).intValue()*((Integer)stack.pop()).intValue() );
                stack.push(res);
                break;
            case Inst.WAIT:
                /*try {
                    robot.delay(((Integer)stack.pop()).intValue());
                    //thread.sleep(((Integer)stack.pop()).longValue());
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }*/
                long retrazo = ((Integer)stack.pop()).longValue();
                while (retrazo > 60000) {
                    robot.delay(60000);
                    retrazo-=60000;
                }
                robot.delay((int)retrazo);
                break;
            case Inst.MOUSEMOVE:
                locTmp = (Location)stack.pop();
                robot.mouseMove(locTmp.x, locTmp.y);
                break;
            case Inst.MOUSEPRESS:
                strTmp = (String)instAct.operador;
                if (strTmp.equals(ScriptParser.BOTON1))
                    robot.mousePress(InputEvent.BUTTON1_MASK);
                else 
                    robot.mousePress(InputEvent.BUTTON3_MASK);
                break;
            case Inst.MOUSERELEASE:
                strTmp = (String)instAct.operador;
                if (strTmp.equals(ScriptParser.BOTON1))
                    robot.mouseRelease(InputEvent.BUTTON1_MASK);
                else 
                    robot.mouseRelease(InputEvent.BUTTON3_MASK);
                break;
            case Inst.KEYPRESS:
                keyTmp = (MacroKey)stack.pop();
                teclasTmp = keyTmp.getAllKey();
                for (i=0; i < teclasTmp.length; i++)
                    robot.keyPress(teclasTmp[i]);
                break;
            case Inst.KEYRELEASE:
                keyTmp = (MacroKey)stack.pop();
                teclasTmp = keyTmp.getAllKey();
                for (i=0; i < teclasTmp.length; i++)
                    robot.keyRelease(teclasTmp[i]);
                break;
            case Inst.SAY:
                int retr = robot.getAutoDelay();
                robot.setAutoDelay(1);
                Object tmpObj = stack.pop();
                if (!(tmpObj instanceof String)) {
                    strTmp = "" + tmpObj.toString();
                    for (i=0; i < strTmp.length(); i++) {
                        keyTmp = new MacroKey(""+strTmp.charAt(i));
                        robot.keyPress(keyTmp.getKey(0));
                        robot.keyRelease(keyTmp.getKey(0));
                    }
                }
                else {
                    strTmp = ((String)tmpObj).toUpperCase();
                    for (i=0; i < strTmp.length(); i++) {
                        //System.out.println((int)strTmp.charAt(i));
                        robot.keyPress((int)strTmp.charAt(i));
                        robot.keyRelease((int)strTmp.charAt(i));
                    }
                }
                robot.keyPress(KeyEvent.VK_ENTER);
                robot.setAutoDelay(retr);
                robot.keyRelease(KeyEvent.VK_ENTER);
                break;                
            case Inst.DUP:
                stack.push(stack.lastElement());
                break;                
            case Inst.JUMP:
                pc = ((Integer)instAct.operador).intValue();
                return ;
            case Inst.JUMPZ:
                int remo = ((Integer)stack.pop()).intValue();
                //System.out.print("pc: " + pc + " instruccion: " + instAct);
                if (remo == 0) {
                    pc = ((Integer)instAct.operador).intValue();
                    //pc = ((Integer)((Simbol)instAct.operador).value).intValue();
                    return ;
                }
                break;
            case Inst.POP:
                stack.pop();
                break;
            case Inst.EXIT:
                pc = inst.size();
                return ;
            case Inst.IODELAY:
                robot.setAutoDelay(((Integer)stack.pop()).intValue());
                break;
            case Inst.LOADDELAY:
                stack.push(new Integer(robot.getAutoDelay()));
                break;
            case Inst.LOADNATIVEMODE:
                stack.push(new Integer( (robot.isNative() && robot.isNativeMode() && robot.isActWin()) ? 1 : 0 ));
                break;
            case Inst.PRINT:
                System.out.println("Print: " + stack.pop());
                break;
            case Inst.GETCOLOR:
                locTmp = (Location)stack.pop();
                stack.push(new MyColor(robot.getPixelColor(locTmp.x, locTmp.y)));
                //System.err.println((MyColor)stack.lastElement());
                break;
            case Inst.CMP:
                //System.out.println();
                stack.push(cmp(stack.pop(), stack.pop(), ((Integer)instAct.operador).intValue()));
                break;
            case Inst.ALU:
                Object a = stack.pop();
                Object b = stack.pop();
                stack.push(alu(a, b, ((Integer)instAct.operador).intValue()));
                //stack.push(alu(stack.pop(), stack.pop(), ((Integer)instAct.operador).intValue()));
                break;
            case Inst.CALLSUB:
                SimbolTable tmpTable = (SimbolTable)instAct.operador;
                Object tmp;
                for (i=((Integer)stack.pop()).intValue(); i > 0 ; i--)
                {
                    tmp = stack.pop();
                    if (tmp instanceof Simbol)
                    {
                        simbTmp = table.find(((Simbol)tmp).token.image);
                        tmp = simbTmp.value;
                        simbTmp = new Simbol(simbTmp.value, simbTmp.type, new Sindarin.ScriptParser.Token(ScriptParser.PARAM, "$" + i));
                        simbTmp.value = tmp;
                    }
                    else
                        simbTmp = new Simbol(tmp, ScriptParser.PARAM, new Sindarin.ScriptParser.Token(ScriptParser.PARAM, "$" + i));
                    tmpTable.newSimbol(simbTmp);
                }
                stack.push(table);
                table = tmpTable;
                break;
            case Inst.RETURN:
                SimbolTable tableTmp = (SimbolTable) stack.pop();
                Object []tmpArray = (Object [])instAct.operador;
                for (i = 0; i < tmpArray.length; i++)
                {
                    simbTmp = tableTmp.find((String)((Object [])tmpArray[i])[0]);
                    simbTmp.value = table.find("$" + ((Integer)((Object [])tmpArray[i])[1]).intValue()).value;
                }            
                table = tableTmp;
                break;
            case Inst.LOADDATE:
                calendar = Calendar.getInstance();
                stack.push(new Integer(calendar.get(Calendar.DAY_OF_MONTH)));
                stack.push(new Integer(calendar.get(Calendar.MONTH)));
                stack.push(new Integer(calendar.get(Calendar.YEAR)));
                break;
            case Inst.LOADTIME:
                calendar = Calendar.getInstance();
                stack.push(new Integer(calendar.get(Calendar.SECOND)));
                stack.push(new Integer(calendar.get(Calendar.MINUTE)));
                stack.push(new Integer(calendar.get(Calendar.HOUR_OF_DAY)));
                break;
            case Inst.BEEP:
                tool.beep();
                break;
            case Inst.GETCOMPONENT:
                Object o = stack.pop();
                int component = ((Integer)stack.pop()).intValue();
                if (o instanceof Location)
                {
                    switch (component)
                    {
                        case 1:
                            stack.push(new Integer(((Location)o).x));
                            break;
                        case 2:
                            stack.push(new Integer(((Location)o).y));
                            break;
                        default:
                            System.err.println(java.util.ResourceBundle.getBundle("Sindarin/config").getString("ERROR_ACCESS_COMPONEN_NUMBER1") + component + java.util.ResourceBundle.getBundle("Sindarin/config").getString("ERROR_ACCESS_COMPONEN_NUMBER2"));
                            pc = inst.size();
                            break;
                    }
                }
                else
                    switch (component)
                    {
                        case 1:
                            stack.push(new Integer(((MyColor)o).color.getRed()));
                            break;
                        case 2:
                            stack.push(new Integer(((MyColor)o).color.getGreen()));
                            break;
                        case 3:
                            stack.push(new Integer(((MyColor)o).color.getBlue()));
                            break;
                        default:
                            System.err.println(java.util.ResourceBundle.getBundle("Sindarin/config").getString("ERROR_ACCESS_COMPONEN_NUMBER1") + component + java.util.ResourceBundle.getBundle("Sindarin/config").getString("ERROR_ACCESS_COLOR_NUMBER2"));
                            pc = inst.size();
                            break;
                    }
                break;
            default:
                System.err.println(java.util.ResourceBundle.getBundle("Sindarin/config").getString("ERROR_UNKNOWN_OPCODE_NO_INTERPRETABLE") + instAct);
        }
        pc++;
    }

    public Object cmp(Object a, Object b, int op) {
        switch (op) {
            case ScriptParserConstants.GREAT:
                return new Integer( (((Integer)a).intValue() < ((Integer)b).intValue()) ? 1:0);
            case ScriptParserConstants.GREATEQUAL:
                return new Integer( (((Integer)a).intValue() <= ((Integer)b).intValue()) ? 1:0);
            case ScriptParserConstants.LESS:
                return new Integer( (((Integer)a).intValue() > ((Integer)b).intValue()) ? 1:0);
            case ScriptParserConstants.LESSEQUAL:
                return new Integer( (((Integer)a).intValue() >= ((Integer)b).intValue()) ? 1:0);
            case ScriptParserConstants.EQUAL:
                if (a instanceof Integer)
                    return new Integer( (((Integer)a).intValue() == ((Integer)b).intValue()) ? 1:0);
                if (a instanceof String)
                    return new Integer( (((String)a).equals((String)b)) ? 1:0);
                if (a instanceof Location)
                    return new Integer( (((Location)a).equals((Location)b)) ? 1:0);
                if (a instanceof MacroKey)
                    return new Integer( (((MacroKey)a).equals((MacroKey)b)) ? 1:0);
                if (a instanceof MyColor)
                    return new Integer( (((MyColor)a).equals((MyColor)b)) ? 1:0);
                break;
            case ScriptParserConstants.NOTEQUAL:
                Integer res = (Integer)cmp(a, b, ScriptParserConstants.EQUAL);
                return new Integer((res.intValue() == 0) ? 1 : 0);
            default:
                System.err.println(java.util.ResourceBundle.getBundle("Sindarin/config").getString("ERROR_UNKNOWN_INSTRUTION"));
        }
        return new Integer(0);
    }
    
    public Object alu(Object a, Object b, int op) {
        switch (op) {
            case ScriptParserConstants.AND:
                //return new Integer( ((((Integer)a).intValue() != 0) && (((Integer)b).intValue() != 0)) ? 1:0);
                return new Integer( ((Integer)a).intValue() & ((Integer)b).intValue());
            case ScriptParserConstants.OR:
                //return new Integer( ((((Integer)a).intValue() == 1) || (((Integer)b).intValue() == 1)) ? 1:0);
                return new Integer( ((Integer)a).intValue() | ((Integer)b).intValue());
            case ScriptParserConstants.ADD:
                if (a instanceof String)
                    //return new String(((String)b) + ((String)a));
                    return new String(b + ((String)a));
                if (b instanceof String)
                    //return new String(((String)b) + ((String)a));
                    return new String((String)b + a);
                if (a instanceof Integer)
                    return new Integer(((Integer)b).intValue() + ((Integer)a).intValue());
                if (a instanceof Location)
                    return ((Location)b).add((Location)a);
                if (a instanceof MacroKey)
                    return ((MacroKey)b).add((MacroKey)a);
                if (a instanceof MyColor)
                    return ((MyColor)b).add((MyColor)a);
            case ScriptParserConstants.SUB:
                if (a instanceof Integer)
                    return new Integer(((Integer)b).intValue() - ((Integer)a).intValue());
                if (a instanceof Location)
                    return ((Location)b).sub((Location)a);
                if (a instanceof MacroKey)
                    return ((MacroKey)b).sub((MacroKey)a);
                if (a instanceof MyColor)
                    return ((MyColor)b).sub((MyColor)a);
            case ScriptParserConstants.MULTIPLY:
                return new Integer(((Integer)b).intValue() * ((Integer)a).intValue());
            case ScriptParserConstants.DIV:
                return new Integer(((Integer)b).intValue() / ((Integer)a).intValue());
            case ScriptParserConstants.MOD:
                return new Integer(((Integer)b).intValue() % ((Integer)a).intValue());
            default:
                System.err.println(java.util.ResourceBundle.getBundle("Sindarin/config").getString("ERROR_UNKNOWN_ARITMETIC_INSTRUTION"));
        }
        return null;
    }
    
    public void stop() {
        stopped = true;
    }
    
    public void pause() {
        paused = true;
    }
    
    public void resume() {
        paused = false;
    }
    
    public boolean isPaused() {
        return paused;
    }
}